﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;

namespace ZExpressions.Keygen
{
    class LicenseInfo
    {
        internal static readonly Dictionary<string, string> products = new Dictionary<string, string>
            {
                {
                    "100",
                    ".NET EF Extensions - Bundle"
                },
                {
                    "101",
                    ".NET EF Extensions - SQL Server"
                },
                {
                    "102",
                    ".NET EF Extensions - SQL Compact"
                },
                {
                    "103",
                    ".NET EF Extensions - MySQL"
                },
                {
                    "104",
                    ".NET EF Extensions - SQLite"
                },
                {
                    "105",
                    ".NET EF Extensions - PostgreSQL"
                },
                {
                    "106",
                    ".NET EF Extensions - Oracle"
                },
                {
                    "107",
                    ".NET EF Extensions - Firebird"
                },
                {
                    "300",
                    ".NET Bulk Operations - Bundle"
                },
                {
                    "301",
                    ".NET Bulk Operations - SQL Server"
                },
                {
                    "302",
                    ".NET Bulk Operations - SQL Compact"
                },
                {
                    "303",
                    ".NET Bulk Operations - MySQL"
                },
                {
                    "304",
                    ".NET Bulk Operations - SQLite"
                },
                {
                    "305",
                    ".NET Bulk Operations - PostgreSQL"
                },
                {
                    "306",
                    ".NET Bulk Operations - Oracle"
                },
                {
                    "307",
                    ".NET Bulk Operations - Firebird"
                },
                {
                    "400",
                    "Compiler Expression.NET - Bundle"
                },
                {
                    "500",
                    "Eval Expression.NET"
                },
                {
                    "600",
                    "Eval SQL.NET"
                },
                {
                    "700",
                    "Dapper Plus - Bundle"
                },
                {
                    "701",
                    "Dapper Plus - SQL Server"
                },
                {
                    "702",
                    "Dapper Plus - SQL Compact"
                },
                {
                    "703",
                    "Dapper Plus - MySQL"
                },
                {
                    "704",
                    "Dapper Plus - SQLite"
                },
                {
                    "705",
                    "Dapper Plus - PostgreSQL"
                },
                {
                    "706",
                    "Dapper Plus - Oracle"
                },
                {
                    "707",
                    "Dapper Plus - Firebird"
                },
                {
                    "900",
                    "EF Classic - Bundle"
                },
                {
                    "901",
                    "EF Classic - SQL Server"
                },
                {
                    "902",
                    "EF Classic - SQL Compact"
                },
                {
                    "903",
                    "EF Classic - MySQL"
                },
                {
                    "904",
                    "EF Classic - SQLite"
                },
                {
                    "905",
                    "EF Classic - PostgreSQL"
                },
                {
                    "906",
                    "EF Classic - Oracle"
                },
                {
                    "907",
                    "EF Classic - Firebird"
                },
                {
                    "950",
                    "EF Classic - Standalone - Bundle"
                },
                {
                    "951",
                    "EF Classic - Standalone - SQL Server"
                }
            };

        public int productCode;
        public string random;
        public string productName;
        public string name;
        public DateTime expreAt;

        public LicenseInfo(string name, int productCode, string productName, DateTime expireAt)
        {
            if (expireAt < new DateTime(2021, 3, 10)) expireAt = new DateTime(2099, 12, 31);
            var r = new Random(Environment.TickCount);
            random = r.Next(1000, 9999).ToString();
            this.name = name;
            this.productName = productName;
            this.productCode = productCode;
            this.expreAt = expireAt;
        }

        public LicenseInfo(string name, int productCode, DateTime expireAt) : this(name, productCode, GetProductNameByCode(productCode), expireAt)
        {

        }

        private static string GetProductNameByCode(int productCode)
        {
            if (products.ContainsKey(productCode.ToString())) return products[productCode.ToString()];
            else return "";
        }

        public string GetLicenseNameString()
        {
            if (this.productCode == 0)
            {
                return string.Format("{0};{1};{2}", this.productName, this.random, this.name);
            }
            return string.Format("{0};{1};{2};{3}", new object[]
            {
                    this.productCode,
                    this.productName,
                    this.random,
                    this.name
            });
        }


        public string GetLicenseKeyString()
        {
            if (this.productCode == 0)
            {
                return string.Format("{0};{1}{2}{3};{4};{5}", new object[]
                {
                        this.productName,
                        this.expreAt.Year,
                        this.expreAt.Month.ToString("00"),
                        this.expreAt.Day.ToString("00"),
                        this.random,
                        this.name
                });
            }
            return string.Format("{0}{1};{2}{3}{4};{5};{6}", new object[]
            {
                    this.productCode,
                    this.productName,
                    this.expreAt.Year,
                    this.expreAt.Month.ToString("00"),
                    this.expreAt.Day.ToString("00"),
                    this.random,
                    this.name
            });
        }

        private string CreateLicenseKeyTpl()
        {
            return new string('0', 36);
        }

        private string GetIndexSet(string licenseName)
        {
            var result = (licenseName.ToCharArray().ToList().Sum(i => (int)i) * 1981 % 1000000).ToString("000000");
            return result;
        }

        private string SetExpireAt(string licenseKey, DateTime expireAt, string indexSet)
        {
            var expireVal = expireAt.ToString("yyMMdd");
            var result = licenseKey.ToCharArray().ToList();
            for (int i = 5; i >= 0; i--)
            {
                var index = Convert.ToInt32(indexSet[i].ToString());
                var value = (char)expireVal[i];

                result.Insert(index, value);
            }
            return new string(result.Take(36).ToArray());
        }

        public DateTime ReadExpireFromLicenseKey(string licenseName, string licenseKey)
        {
            var resultString = "";
            var indexSet = GetIndexSet(licenseName);
            for (int i = 0; i < 6; i++)
            {
                int num = Convert.ToInt32(indexSet[i].ToString());
                resultString += licenseKey[num].ToString();
                licenseKey = licenseKey.Remove(num, 1);
            }
            var result = new DateTime(2000 + Convert.ToInt32(resultString.Substring(0, 2)), Convert.ToInt32(resultString.Substring(2, 2)), Convert.ToInt32(resultString.Substring(4, 2)));
            return result;
        }

        private string SetSign(string licenseKey, string sign)
        {
            var signVal = sign.ToCharArray();
            var result = licenseKey.ToCharArray().ToList();
            var ptr = 0;
            for (int i = 0; i < 6; i++)
            {
                result[i * 4 + 2] = signVal[ptr];
                result[i * 4 + 3] = signVal[ptr + 1];
                ptr += 2;
            }

            return new string(result.Take(36).ToArray());
        }

        public (string licenseName, string licenseKey) Generate()
        {
            var licenseName = GetLicenseNameString();
            var indexSet = GetIndexSet(licenseName);
            var licenseKey = CreateLicenseKeyTpl();
            licenseKey = SetSign(licenseKey, GetSign());
            licenseKey = SetExpireAt(licenseKey, expreAt, indexSet);
            //var expireAt = ReadExpireFromLicenseKey(licenseName, licenseKey);
            return (GetLicenseName(), licenseKey);
        }

        private string GetLicenseName()
        {
            return $"{random};{productCode}-{name}";
        }

        string GetSign()
        {
            return Hash(GetLicenseKeyString()).Substring(0, 12);
        }

        string Hash(string val)
        {
            byte[] bytes = Encoding.ASCII.GetBytes(val);
            byte[] array = new SHA512CryptoServiceProvider().ComputeHash(bytes);
            StringBuilder stringBuilder = new StringBuilder();
            for (int i = 0; i < 13; i++)
            {
                stringBuilder.Append(array[i + i % 5].ToString("X2"));
            }
            return stringBuilder.ToString();
        }

        public static LicenseInfo Create(string name, int productCode, string productName, DateTime expireAt)
        {
            return new LicenseInfo(name, productCode, productName, expireAt);
        }

        public static LicenseInfo Create(string name, DateTime expireAt, int productCode = 0)
        {
            return new LicenseInfo(name, productCode, expireAt);
        }
    }

}
